/*

Copyright (C) 2000 - 2008 Christian Kreibich <christian@whoop.org>.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies of the Software and its documentation and acknowledgment shall be
given in the documentation and software packages that this Software was
used.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

*/
#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

#include <unistd.h>
#include <stdlib.h>

#include <nd.h>
#include <nd_clipboard.h>
#include <nd_gui.h>
#include <nd_tp.h>
#include <nd_trace_registry.h>
#include <support.h>

static LND_Packet *cb = NULL;
static LND_Packet *cb_end = NULL;
static int         cb_size;
static char       *cb_content_string = NULL;

static void
nd_clipboard_free(void)
{
  LND_Packet *p, *p2;

  if (cb)
    {
      p = cb;
      
      while (p)
	{
	  p2 = p;
	  p = p->next;
	  libnd_packet_free(p2);
	}

      cb = cb_end = NULL;
      cb_size = 0;
    }
}


/* This function puts a string in the X cut buffer representing
 * our current packet selection for the given trace.
 */
static void
clipboard_create_content_string(LND_Trace *trace)
{
  ND_Trace *trace_gui;
  GtkCList *list;
  GList    *sel, *sel_copy = NULL;
  gchar    *text, *ptr;
  int       strlength = 0, len;

  if (!trace || !nd_trace_get(trace) || !nd_trace_get(trace)->list)
    return;

  trace_gui = nd_trace_get(trace);

  g_free(cb_content_string);
  list = GTK_CLIST(trace_gui->list);

  /* Go through all selected rows and record the string lengths,
     also build a copy of the list, we'll sort that one later. */

  for (sel = list->selection; sel; sel = g_list_next(sel))
    {
      sel_copy = g_list_prepend(sel_copy, GINT_TO_POINTER(GTK_CLIST_ROW(sel)));
      gtk_clist_get_text(list, (int) GTK_CLIST_ROW(sel), 0, &text);
      strlength += strlen(text) + 1;
    }

  cb_content_string = g_new0(gchar, strlength + 1);
  if (!cb_content_string)
    return;

  sel_copy = g_list_sort(sel_copy, libnd_misc_int_cmp);

  ptr = cb_content_string;
  
  /* We now have a string of sufficient length and a list of rows
     sorted by increasing row number. Iterate again and put together
     our selection content! */

  for (sel = sel_copy; sel; sel = g_list_next(sel), ptr++)
    {
      gtk_clist_get_text(list, (int) GTK_CLIST_ROW(sel), 0, &text);
      len = strlen(text);
      memcpy(ptr, text, len);
      ptr += len;
      *ptr = '\n';
    }
  
  *ptr = '\0';


  /* Make sure that we now own the selection */
  gtk_selection_owner_set(nd_toplevel_window,
			  GDK_SELECTION_PRIMARY,
			  GDK_CURRENT_TIME);

  /* And clean our mess up. */
  g_list_free(sel_copy);
}


/* This callback gets called when a different app wants to
 * know the contents of the primary selection and we've
 * signaled that we possess it. It creates a string out of
 * all currently selected packets in the current trace.
 */
static void
clipboard_selection_handle(GtkWidget *list,
			   GtkSelectionData *selection_data,
			   gpointer data)
{
  /* Let the requesting app know of our selection */

  if (!cb_content_string)
    return;

  gtk_selection_data_set(selection_data, GDK_SELECTION_TYPE_STRING, 8,
			 (guchar *) cb_content_string, strlen(cb_content_string));


  return;
  TOUCH(list);
  TOUCH(data);
}


void
nd_clipboard_init(void)
{
  D_ENTER;

  /* Selection stuff -- register our callback that returns
   * our concept of NEtdude's current selection, i.e. the
   * tcpdump output of the currently selected packets:
   */
  gtk_selection_add_target(nd_toplevel_window,
			   GDK_SELECTION_PRIMARY,
			   GDK_SELECTION_TYPE_STRING, 1);

  gtk_signal_connect(GTK_OBJECT(nd_toplevel_window), "selection_get",
		     GTK_SIGNAL_FUNC(clipboard_selection_handle),
		     NULL);

  D_RETURN;
}


void  
nd_clipboard_cut(void)
{
  LND_PacketIterator pit;
  LND_Packet *p, *c = NULL;
  LND_Trace  *trace;
  char message[MAXPATHLEN];

  return_if_no_current_trace(trace);
  if (! libnd_tpm_get_sel(trace->tpm))
    return;

  nd_clipboard_free();
  clipboard_create_content_string(trace);

  for (libnd_pit_init_mode(&pit, trace, LND_PACKET_IT_SEL_R); libnd_pit_get(&pit); libnd_pit_next(&pit))
    {
      p = libnd_pit_get(&pit);

      if (p->prev)
	{
	  if (p->next)
	    {
	      p->prev->next = p->next;
	      p->next->prev = p->prev;
	    }
	  else
	    {
	      p->prev->next = NULL;
	    }
	}
      else if (p->next)
	{
	  trace->tpm->current->pl = p->next;
	  p->next->prev = NULL;
	}
      else
	{
	  trace->tpm->current->pl = NULL;
	  trace->tpm->current->pl_end = NULL;
	}
      
      if (c)
	{
	  c->next = p;
	  p->prev = c;
	  c = p;
	}
      else
	{
	  cb = c = p;
	}
    }
  
  cb_end = c;
  cb_end->next = NULL;
  trace->tpm->current->num_packets -= trace->tpm->current->sel.size;
  cb_size = trace->tpm->current->sel.size;
  trace->tpm->current->sel.size = 0;
  trace->tpm->current->sel.pl = NULL;
  
  libnd_trace_set_dirty(trace, TRUE);
  nd_gui_list_remove_selected_rows(trace);

  g_snprintf(message, MAXPATHLEN, _("Cut %i packet(s) into clipboard."), cb_size);
  nd_gui_statusbar_set(message);
  nd_gui_sync_edit_menu();
}


void  
nd_clipboard_copy(void)
{
  LND_PacketIterator pit;
  LND_Packet *p, *c;
  LND_Trace *trace;
  char message[MAXPATHLEN];
  int  copied = 0;

  return_if_no_current_trace(trace);

  nd_clipboard_free();

  libnd_pit_init_mode(&pit, trace, LND_PACKET_IT_SEL_R);
  c = libnd_packet_duplicate(libnd_pit_get(&pit));
  c->part = libnd_pit_get(&pit)->part;
  libnd_pit_next(&pit);

  if (!c)
    return;

  cb = c;
  copied++;
  clipboard_create_content_string(trace);

  for ( ; libnd_pit_get(&pit); libnd_pit_next(&pit))
    {                        
      if ( (p = libnd_packet_duplicate(libnd_pit_get(&pit))))
	{
	  c->next = p;
	  p->part = libnd_pit_get(&pit)->part;
	  p->prev = c;
	  c = p;
	  cb_end = c;
	  
	  copied++;
	}      
    }  
  
  cb_size = copied;
  
  g_snprintf(message, MAXPATHLEN, _("Copied %i packet(s) into clipboard."), cb_size);
  nd_gui_statusbar_set(message);
  nd_gui_sync_edit_menu();
}


void  
nd_clipboard_paste(void)
{
  LND_Trace  *trace, *cb_trace;
  GtkCList   *clist;
  LND_Packet *p, *p2, *c, *cb_copy = NULL, *current;
  int         index = 0, pasted = 1;
  char        message[MAXPATHLEN];

  if (!cb)
    return;

  return_if_no_current_trace(trace);
  D_ENTER;

  current = nd_trace_get_current_packet(trace);
  clist = GTK_CLIST(nd_trace_get(trace)->list);
  D_ASSERT_PTR(clist);

  /* Let's clear the selection first. It looks pretty confusing if
   * something is inserted in the middle of it and a selection is "ripped"
   * in two ...
   */
  libnd_tp_clear_selection(trace->tpm->current);

  /* If the trace we're pasting to is initialized already, make sure the
   * pasted packets have the same linktype. Also adjust the maximum snaplen
   * if the clipboard packets are larger than the ones already present:
   */
  cb_trace = libnd_packet_get_trace(cb);
  D_ASSERT_PTR(cb_trace);

  if (libnd_trace_initialized(trace))
    {
      if (cb_trace->tcpdump.pfh.linktype != 
	  trace->tcpdump.pfh.linktype)
	{
	  g_snprintf(message, MAXPATHLEN,
		     _("Packets in clipboard are of different "
		       "link layer type (0x%04x)\nthan packets in trace (0x%04x), "
		       "pasting aborted. "),
		     cb_trace->tcpdump.pfh.linktype,
		     trace->tcpdump.pfh.linktype);
	  nd_dialog_message(_("Packet Compatibility Problem"),
			    message, TRUE);
	  D_RETURN;
	}
      
      if (cb_trace->tcpdump.pfh.snaplen >
	  trace->tcpdump.pfh.snaplen)
	{
	  D(("Adjusting snaplen to %u\n", cb_trace->tcpdump.pfh.snaplen));
	  trace->tcpdump.pfh.snaplen = cb_trace->tcpdump.pfh.snaplen;
	}
    }
  else
    {
      char *tmp;
      pcap_dumper_t *pd;

      D(("First paste --> initializing trace.\n"));

      /* This is a bit tricky -- we want an empty input trace file with the
       * same properties (snaplen etc) as the trace that contains the packets
       * that we're pasting.
       *
       * So we get a temp name based on that original trace file, create
       * a pcap dumper for it (using the original trace file's pcap handle),
       * and then initialize the new empty trace using that trace.
       */
      if (! (tmp = libnd_misc_get_tmpfile(cb_trace->filename)))
	D_RETURN;
      
      if (! (pd = pcap_dump_open(pcapnav_pcap(cb_trace->tpm->current->pcn), tmp)))
	{
	  g_free(tmp);
	  D_RETURN;
	}

      pcap_dump_close(pd);
      libnd_trace_init(trace, tmp);
      g_free(tmp);
    }
  
  /* Obtain the index of the currently selected packet, if any,
   * so that we can insert the new tcpdump lines for the inserted
   * packets into the list at the correct location.
   */
  if (current)
    index = libnd_packet_get_index(current);
  
  /* Now duplicate the clipboard contents.
   */
  cb_copy = c = libnd_packet_duplicate(cb);
  if (!cb_copy)
    D_RETURN;

  cb_copy->part = trace->tpm->current;
  
  for (p = cb->next; p; p = p->next)
    {
      if (! (p2 = libnd_packet_duplicate(p)))
	break;

      c->next = p2;
      p2->prev = c;
      c = p2;
      c->part = trace->tpm->current;
      pasted++;
    }

  libnd_tp_insert_packets(trace->tpm->current, cb_copy, index);

  /* Make the old current packet the currently selected one. */
  if (current)
    nd_gui_list_select_packet(trace, libnd_packet_get_index(current));

  g_snprintf(message, MAXPATHLEN, _("Pasted %i packet(s) from clipboard."), pasted);
  nd_gui_statusbar_set(message);
  D_RETURN;
}


int   
nd_clipboard_occupied(void)
{
  return (cb != NULL);
}

void
nd_clipboard_set_content(const char *content)
{
  g_free(cb_content_string);
  cb_content_string = g_strdup(content);
  
  /* Make sure that we now own the selection */
  gtk_selection_owner_set(nd_toplevel_window,
			  GDK_SELECTION_PRIMARY,
			  GDK_CURRENT_TIME);
}
